
/*
 * @Author: c
 * @Date: 2023-12-03 02:18:10
 * @LastEditTime: 2024-01-17 01:19:05
 * @LastEditors: c
 * @Description: 用户
 * @FilePath: \music-web-backend\src\core\system\user\user.service.ts
 * https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isNumber } from 'lodash';

import { PageResDto } from 'src/common/dto/page.dto';
import { GenderEnum, GenderEnumDesc } from 'src/common/enums/gender.enum';
import { UserNameExistException, UserNameNotFoundException } from 'src/common/exception/business.exception';
import { Brackets, DataSource, InsertResult, Repository, WhereExpressionBuilder } from 'typeorm';
import { CreateUserDto, UserForm } from './dto/create-user.dto';
import { UserPageQuery } from './dto/search-user.dto';
import { User } from './entities/user.entity';
import { UserExportVO, UserPageVo, UserVo } from './vo/user.vo';

import { UserRole } from './entities/user_role.entity';

import { ConfigService } from '@nestjs/config';
import { plainToClass } from 'class-transformer';
import { zip } from 'compressing';
import { Response } from 'express';
import { join } from 'path';
import { EXCEPTION } from 'src/common/const/constants';
import { ExportExcelDto } from 'src/common/dto/excel.dto';
import { CommonException } from 'src/common/exception/common.exception';
import { BcryptService } from 'src/common/service/bcrypt.service';
import { ExcelService } from 'src/common/service/excel.service';
import { Role } from '../roles/entities/role.entity';
import { RolesService } from '../roles/roles.service';
import { UserExcelConst } from './const/excel.const';

@Injectable()
export class UserService {



  constructor(
    @InjectRepository(User) private userRepository: Repository<User>,
    @InjectRepository(UserRole) private userRoleRepository: Repository<UserRole>,
    private roleServie: RolesService,
    private configService: ConfigService,
    private dataSource: DataSource,
    // private excelSource: ExcelService<UserExportVO>,
  ) { }


  /**
   * 导入用户列表
   * @param file 
   */
  async importUsers(file: Express.Multer.File) {

    // const { buffer } = file; // file为前端上传的excel
    // const workbook = new Workbook();
    // 读取上传的文件
    // const workbook2 = await workbook.xlsx.load(buffer)
    // let res1 = []
    // // 获取workbook的第一个sheet
    // workbook2.eachSheet((sheet, index1) => {
    //   console.log('工作表' + index1);

    //   const res: RowValues[] = sheet.getSheetValues();
    // })

    const res = await ExcelService.import(file)


    //建立连接 创建runner
    const queryRunner = this.dataSource.createQueryRunner();
    queryRunner.connect();
    //开启事务
    await queryRunner.startTransaction();

    try {

      const userList: User[] = plainToClass(User, res.map(({ roleDesc, ...rest }) => {

        return { ...rest, gender: rest.gender === GenderEnumDesc.MAN ? 1 : 2, email: rest.email.text }
      }))



      const insertUsersResult: InsertResult = await queryRunner.manager.
        createQueryBuilder()
        .insert()
        .into(User)
        .values(userList)
        .execute();

      const userRoles: UserRole[] = []

      for await (const user of insertUsersResult.identifiers) {
        const role: Role = await this.roleServie.findOneByName(user.roleDesc)
        const userRole = new UserRole(user.id, role.id);
        userRole.roleId = role.id
        userRole.userId = user.id
        userRoles.push(userRole)
      }

      const insertResult = await queryRunner.manager.
        createQueryBuilder()
        .insert()
        .into(UserRole)
        .values(userRoles)
        .execute();


      // 现在提交事务：
      await queryRunner.commitTransaction()
    } catch (error) {
      Logger.error(error)
      await queryRunner.rollbackTransaction()
      throw new CommonException(EXCEPTION.COMMON)
    } finally {
      await queryRunner.release()
    }



    return res
  }

  async downloadTemplate(res: Response) {
    // const uploadDir = this.configService.get('file').root;
    const uploadDir = join(process.cwd(), 'dist/resources/用户导入模板.xlsx')
    const tarStream = new zip.Stream();  //文件压缩流
    await tarStream.addEntry(uploadDir); //压缩文件路径

    res.setHeader('Content-Type', 'application/octet-stream');


    res.setHeader(
      'Content-Disposition',
      `attachment; filename=${encodeURI('用户导入模板1.zip')}`,
    );

    tarStream.pipe(res)
    // return { filename: '用户导入模板.xlsx', tarStream };
  }


  /**
   * 导出用户列表
   * @param userPage 
   */
  async exportUsers(res: Response, { pageNum = 1, pageSize = 10, keywords, status, startTime, endTime }: UserPageQuery): Promise<any> {

    const builder = this.userRepository
      .createQueryBuilder('user')
      // .select('user.username')
      // .select('user.nickname')
      // .select('user.mobile')
      // .select('user.create_time', 'createTime')
      .select(['user.username as username',
        'user.nickname as nickname',
        'user.mobile as mobile',
        'user.email as email',
        'user.create_time as createTime',
        `CASE user.gender
              WHEN 1 THEN '男'
              WHEN 2 THEN '女'
              ELSE '未知'
              END genderLabel`])
      .where('user.username != :username', { username: 'root' })
      .andWhere('user.deleted = :deleted', { deleted: 0 })
      .printSql();

    if (keywords) {
      builder.andWhere(new Brackets((qb: WhereExpressionBuilder) => {
        qb.where('user.username like :keywords', { keywords: `%${keywords}%` })
          .orWhere('user.nickname like :keywords', { keywords: `%${keywords}%` })
          .orWhere('user.mobile like :keywords', { keywords: `%${keywords}%` })
      }))
    }

    if (status) {
      builder.andWhere('user.status = :status', { status })
    }

    if (startTime) {
      builder.andWhere('user.create_time >= :startTime', { startTime })
    }
    if (endTime) {
      builder.andWhere('user.create_time <= :endTime', { endTime })
    }

    builder.groupBy("user.id")



    const total = await builder.getCount()
    const users = await builder.offset((pageNum - 1) * pageSize).limit(pageSize).getRawMany();
    const userss: UserExportVO[] = plainToClass(UserExportVO, users)

    const exportExcelDto: ExportExcelDto<UserExportVO> = new ExportExcelDto()

    const keys = Object.keys(UserExcelConst).map(k => {
      return { header: UserExcelConst[k].desc, key: k, width: UserExcelConst[k].width }
    });



    exportExcelDto.columns = keys
    exportExcelDto.result = userss


    // const workbook = new Workbook();

    // const worksheet = workbook.addWorksheet('guang111');

    // worksheet.columns = [
    //   { header: 'ID', key: 'id', width: 20 },
    //   { header: '姓名', key: 'name', width: 30 },
    //   { header: '出生日期', key: 'birthday', width: 30 },
    //   { header: '手机号', key: 'phone', width: 50 }
    // ];

    // const data = [
    //   { id: 1, name: '光光', birthday: new Date('1994-07-07'), phone: '13255555555' },
    //   { id: 2, name: '东东', birthday: new Date('1994-04-14'), phone: '13222222222' },
    //   { id: 3, name: '小刚', birthday: new Date('1995-08-08'), phone: '13211111111' }
    // ]
    // worksheet.addRows(data);

    // worksheet.eachRow((row, rowIndex) => {
    //   row.eachCell(cell => {
    //     if (rowIndex === 1) {
    //       cell.style = {
    //         font: {
    //           size: 10,
    //           bold: true,
    //           color: { argb: 'ffffff' }
    //         },
    //         alignment: { vertical: 'middle', horizontal: 'center' },
    //         fill: {
    //           type: 'pattern',
    //           pattern: 'solid',
    //           fgColor: { argb: '000000' }
    //         },
    //         border: {
    //           top: { style: 'dashed', color: { argb: '0000ff' } },
    //           left: { style: 'dashed', color: { argb: '0000ff' } },
    //           bottom: { style: 'dashed', color: { argb: '0000ff' } },
    //           right: { style: 'dashed', color: { argb: '0000ff' } }
    //         }
    //       }
    //     } else {
    //       cell.style = {
    //         font: {
    //           size: 10,
    //           bold: true,
    //         },
    //         alignment: { vertical: 'middle', horizontal: 'left' },
    //         border: {
    //           top: { style: 'dashed', color: { argb: '0000ff' } },
    //           left: { style: 'dashed', color: { argb: '0000ff' } },
    //           bottom: { style: 'dashed', color: { argb: '0000ff' } },
    //           right: { style: 'dashed', color: { argb: '0000ff' } }
    //         }
    //       }
    //     }
    //   })
    // })





    // return new Blob([buffer], { type: 'application/vnd.ms-excel' }); // 前端接受到的数据格式为{type: 'buffer', data: []}
    await ExcelService.export<UserExportVO>(res, exportExcelDto)
  }

  /**
   * 用户-修改密码
   * @param userId 
   * @param password 
   */
  async updatePassword(userId: number, password: string) {
    //修改用户
    try {
      await this.userRepository
        .createQueryBuilder()
        .update(User)
        .set({ password: await BcryptService.hash(password) })
        .where('id = :id', { id: userId })
        .execute()
    } catch (error) {
      throw new CommonException(EXCEPTION.COMMON)
    }

  }


  /**
   * 用户-修改
   * @param currentUser 当前登录的用户
   * @param userId 
   * @param userForm 
   */
  async updateUser(currentUser: User, userId: number, userForm: UserForm) {
    const quser: User = await this.findOneByUserName(userForm.username)

    if (quser && (quser.id !== userId)) {
      throw new UserNameExistException(EXCEPTION.USER.EXIST);

    }
    const { roleIds, ...userF } = userForm
    const user: User = Object.assign(userF)

    //建立连接 创建runner
    const queryRunner = this.dataSource.createQueryRunner();
    queryRunner.connect()

    // lets now open a new transaction: 开启事务
    await queryRunner.startTransaction()

    try {
      //修改用户
      const updateRes = await this.userRepository
        .createQueryBuilder()
        .update(User)
        .set(user)
        .where('id = :id', { id: userId })
        .execute()

      //先删除用户的角色

      const deleteRes = await queryRunner.manager
        .createQueryBuilder()
        .delete()
        .from(UserRole)
        .where("userId = :userId", { userId })
        .execute();
      if (deleteRes.affected >= 0) {
        //批量新增用户角色
        // const rids: number[] = userForm.roleIds.split(',').map(r => +r);
        const rids: number[] = userForm.roleIds.map(r => +r);
        const userRoles: UserRole[] = rids.map(r => ({ userId: quser.id, roleId: r }));
        const insertResult = await queryRunner.manager.
          createQueryBuilder()
          .insert()
          .into(UserRole)
          .values(userRoles)
          .execute();
      }

      await queryRunner.commitTransaction()

    } catch (error) {
      await queryRunner.rollbackTransaction()
      throw new CommonException(EXCEPTION.COMMON)
    } finally {
      await queryRunner.release()
    }

  }

  /**
   * 用户-注册
   * @param createUser 
   * @returns 
   */
  async singup(createUser: CreateUserDto) {
    const { username } = createUser;

    const existUser = await this.userRepository.findOne({
      where: { username },
    });
    if (existUser) {
      throw new HttpException("用户已存在", HttpStatus.BAD_REQUEST)
    }


    const newUser = await this.userRepository.create(createUser)
    return await this.userRepository.save(newUser);

  }


  /**
   * 用户-新增
   * @param userForm 
   * @returns 
   */
  async saveUser(userForm: UserForm): Promise<InsertResult> {
    const quser: User = await this.findOneByUserName(userForm.username)

    if (quser?.username === userForm.username) {
      throw new UserNameExistException(EXCEPTION.USER.EXIST);

    }

    let insertResult: InsertResult = null
    const runner = await this.dataSource.createQueryRunner()
    runner.connect()
    await runner.startTransaction()

    try {
      // const user: User = Object.assign(userForm)
      const user: User = plainToClass(User, userForm);
      const { id }: User = await runner.manager.save(user);


      // const rids: number[] = userForm.roleIds.split(',').map(r => +r)
      const rids: number[] = userForm.roleIds.map(r => +r)
      const userRoles: UserRole[] = rids.map(r => ({ userId: id, roleId: r }))

      insertResult = await runner.manager.
        createQueryBuilder().
        insert().
        into(UserRole).
        values(userRoles).execute()

      await runner.commitTransaction()
    } catch (error) {
      Logger.error(error)
      await runner.rollbackTransaction()
      throw new CommonException(EXCEPTION.COMMON)
    } finally {
      await runner.release()
    }



    return insertResult

  }

  /**
   * 用户-批量删除
   * @param ids 
   */

  async deleteUsers(ids: string) {
    const idList = ids.split(',')


    // create a new query runner
    // establish real database connection using our new query runner
    const queryRunner = this.dataSource.createQueryRunner()

    queryRunner.connect()
    // lets now open a new transaction:
    await queryRunner.startTransaction()

    // execute some operations on this transaction:
    try {
      for await (const id of idList) {

        await queryRunner.manager.createQueryBuilder()
          .delete()
          .from(User)
          .where("id = :id", { id })
          .execute();
      }

      // commit transaction now:
      await queryRunner.commitTransaction()
    } catch (error) {
      // since we have errors let's rollback changes we made
      await queryRunner.rollbackTransaction()
      throw new CommonException(EXCEPTION.COMMON)
    } finally {
      // you need to release query runner which is manually created:
      await queryRunner.release()
    }

  }

  /**
   * 获取单个用户
   * @param userId 
   */
  getUserFormData(userId: any) {
    return this.findOne(userId);
  }




  /**
   * 用户-分页查询
   * @param param0 
   * @returns 
   */
  async findAll({ pageNum = 1, pageSize = 10, keywords, status, startTime, endTime }: UserPageQuery): Promise<PageResDto<UserPageVo>> {
    // const [users, count] = await this.userRepository.findAndCount({
    //   where:
    //   [{
    //     username: Not('root'),
    //     deleted: Equal(0),
    //     createTime: Between(new Date(startTime),new Date(endTime)),
    //   }],
    //   relations: { roles: true }, skip: (pageNum - 1) * pageSize, take: pageSize
    // });

    const builder = this.userRepository.createQueryBuilder('user').leftJoinAndSelect('user.roles', 'role')
      .where('user.username != :username', { username: 'root' }).andWhere('user.deleted = :deleted', { deleted: 0 }).printSql();

    if (keywords) {
      builder.andWhere(new Brackets((qb: WhereExpressionBuilder) => {
        qb.where('user.username like :keywords', { keywords: `%${keywords}%` })
          .orWhere('user.nickname like :keywords', { keywords: `%${keywords}%` })
          .orWhere('user.mobile like :keywords', { keywords: `%${keywords}%` })
      }))
    }

    if (isNumber(status)) {
      builder.andWhere('user.status = :status', { status })
    }

    if (startTime) {
      builder.andWhere('user.create_time >= :startTime', { startTime })
    }
    if (endTime) {
      builder.andWhere('user.create_time <= :endTime', { endTime })
    }



    const total = await builder.getCount()
    const users = await builder.offset((pageNum - 1) * pageSize).limit(pageSize).getMany();

    const usersPageDto: UserPageVo[] = users.map(user => ({
      id: user.id,
      nickname: user.nickname,
      username: user.username,
      avatar: user.avatar,
      mobile: user.mobile,
      email: user.email,
      status: user.status,
      createTime: user.createTime,
      genderLabel: user.gender === GenderEnum.MAN ? '男' : '女',
      roleNames: user.roles.map(role => role.name).join(',')
    }))
    // const count = await this.userRepository.count();

    return PageResDto.result(usersPageDto, total);
  }

  async getCurrentUserInfo(userId: number): Promise<UserVo> {

    const user: User = await this.userRepository.findOne({ where: { id: userId }, relations: ['roles', 'roles.menus'] })
    let userVo = new UserVo();
    userVo.avatar = user.avatar
    userVo.nickname = user.nickname
    userVo.roles = user.roles.map(role => role.code)
    userVo.userId = user.id
    userVo.perms = user.roles.map(role => role.menus.map(menu => menu.perm).filter(perm => perm)).flat()
    return userVo;
  }
  async findOne(userId: number): Promise<UserForm> {
    const { id, username, nickname, mobile, gender, avatar, email, status, roles }: User = await this.userRepository.findOne({ where: { id: userId }, relations: { roles: true } })

    if (!username) {
      throw new UserNameNotFoundException(EXCEPTION.USER.NOT_FOUND)
    }

    const userForm: UserForm = {
      id,
      username,
      nickname,
      mobile,
      gender,
      avatar,
      email,
      status,
      // roleIds: roles.map(r => r.id).join(',')
      roleIds: roles.map(r => r.id)
    };


    return userForm;
  }

  async findOneByUserName(userName: string): Promise<User> {

    const user: User = await this.userRepository.findOne({ where: { username: userName }, relations: { roles: true } })

    return user;
  }


  async remove(id: number) {
    const deleteRes = await this.userRepository.delete(id)
    return deleteRes;
  }
}


